<?php

/**
 * @brief 树型数据结构操作集
 *
 * 注意:使用此类时,必须先加载SqlHelper Class
 */
class Tree
{

  /** @brief 数据库处理器 */
  protected $dbh;

  /** @brief 配置信息 */
  protected $setting;

  /** @brief 可操作字段 */
  protected $fields; 


  /**
   * @brief 初始化 $dbh,$setting,$fields
   */
  public function __construct($dbh,$setting,$fields)
  {
    $this->dbh = $dbh;
    $this->setting = $setting;
    $this->fields = $fields;
  }


  /** @brief 输入字段处理 
   * 根据type值调用 sql_fields_diff 或sql_condition_convert
   *
   * @param $input_fields 输入数据,可为字符串或数组
   * @param $type 处理方式
   * - in $input_fields里符合要求的字段
   * - out 排除 $input_fields 里的字段
   *
   * @return
   * - string
   * - false
   */
  public function fields_format($input_fields, $type='in')
  {
    if($type == 'in')
    {
      return SqlHelper::fieldsIntersect($input_fields, $this->fields, 'string');
    }
    elseif( $type == 'out')
    {
      return SqlHelper::fieldsDiff($input_fields, $this->fields, 'string');
    }
  }

  /** @brief 根据key获得节点 */
  public function getNodeByKey($input_key, $output_fields='', $output_fields_type='in')
  {
    extract($this->setting);

    //获得输出字段
    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    //获取节点
    if(is_numeric($input_key))
    {
      $sql = "SELECT $fields FROM $table WHERE $key=$input_key LIMIT 1;";  

      $node = $this->dbh->query($sql);

      if(is_array($node) && is_array($node[0]))
      {
        return $node[0];
      }
      else{ return $node;}
    }
    else{return false;}  
  }

  /** @brief 根据其他字段值获得节点
   *
   * @return 
   * - array
   * - false 错误时
   */
  public function getNode($field, $value, $output_fields='', $output_fields_type='in')
  {
    extract($this->setting);

    if(!in_array($field,$this->fields))
    {
      return false;  
    }

    //获得输出字段
    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    $value = SqlHelper::validateValue($value);

    $sql = "SELECT $fields FROM $table WHERE $field=$value LIMIT 1;";  

    $node = $this->dbh->query($sql);

    if (is_array($node) && is_array($node[0])) {return $node[0];}
    else {return $node;}

  }

  /**
   * @brief 通过设定的条件,获得系列节点
   *
   * @param $condition 条件,有如下类型:
   * - array
   * - string
   *
   * @param $condition_type 如何处理$condition,有如下值:
   * - keys 条件为一系列key值
   * - condition 条件为复合条件
   *
   * @param $output_fields 输出的字段,有如下类型:
   * - array
   * - string  格式为: 'field1,field2....'
   *
   * @param $type 处理$output_fields方式,有如下值:
   * - 'in' 输出指定的字段
   * - 'out' 输出指定字段外的所有字段
   * 
   * @return 
   * - array
   * - false
   */
  public function getNodes($condition,$output_fields='',$condition_type='condition',$output_fields_type='in')
  {

    extract($this->setting);

    //获得输出字段
    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);   

    if( $condition_type === 'keys')
    {

      if(is_string($condition))
      {
        $condition = "$key in ($condition)"; 
      }
      elseif(is_array($condition))
      {
        $condition = "$key in (" . implode(',',$condition) . ')' ;
      }
    }

    $sql = "SELECT $fields FROM $table WHERE $condition;";

    $nodes =   $this->dbh->query($sql);
    return $nodes;
  }

  /**
   * @brief 获取根节点
   */
  public function getRootNode($output_fields='',$output_fields_type='in')
  {
    extract($this->setting);

    //获得输出字段
    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);


    $sql = "SELECT $fields 
      FROM $table 
      WHERE $succ = ( SELECT MAX($succ) FROM $table )
      LIMIT 1;";

    $rootNode = $this->dbh->query($sql);

    if( is_array($rootNode) && is_array($rootNode[0]) )
    {
      return $rootNode[0];
    }
    else
    {
      return $rootNode;
    }
  }

  /**
   * @breif 获取直接后代
   */
  public function getICNodes($node, $output_fields='', $output_fields_type='in')
  {
    extract($this->setting);

    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);
    
    $sql = "
      SELECT $fields 
      FROM $table 
      WHERE $parentKey = {$node[$key]};
    ";

    $nodes = $this->dbh->query($sql) ;
    return $nodes;
  }


  /**
   * @brief 获得同层兄弟节点
   */
  public function getSLNodes($input_parentKey, $output_fields='', $output_fields_type='in')
  {
    if(empty($input_parentKey) ){ return false; }

    extract($this->setting);

    $input_parentKey = SqlHelper::validateValue(
        (is_array($input_parentKey))
        ? $input_parentKey[$parentKey]
        : $input_parentKey
        );

    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    $sql = "
      SELECT $fields 
      FROM $table 
      WHERE $key 
      IN ( SELECT $key 
          FROM $table 
          WHERE $parentKey = $input_parentKey
         )  
      ORDER BY $pre ASC;
    ";

    $nodes = $this->dbh->query($sql);
    return $nodes;
  }

  /**
   * @brief 获取所有节点
   */
  public function getAllNodes($output_fields='', $output_fields_type='in')
  {
    extract($this->setting);

    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    $sql  = "
      SELECT $fields 
      FROM $table 
      ORDER BY $pre ASC;
    ";

    $nodes = $this->dbh->query($sql);
    return $nodes;
  }

  /**
   * @brief 返回第一个子节点
   */
  public function getFCNode($input_pre, $output_fields='',$output_fields_type='in')
  {

    if(empty($input_pre) ){ return false; }

    extract($this->setting);

    //判断是否为叶子节点
    if( is_array($input_pre)
        && isset($input_pre[$pre])
        && isset($input_pre[$succ])
        && (intval($input_pre[$succ]) - intval($input_pre[$pre]) === 1)
      )
    {
      return null;
    }

    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    $input_pre = SqlHelper::validateValue(
        (is_array($input_pre))
        ? $input_pre[$pre]
        : $input_pre
        );
    $input_pre += 1;

    $sql = "
      SELECT $fields 
      FROM $table
      WHERE $pre = $input_pre 
      LIMIT 1 ;
    ";

    $nodes = $this->dbh->query($sql);
    return $nodes;
  }

  /**
   * @brief 获得子节点树
   */
  public function getSubNodes(
      $node,
      $output_fields='',
      $output_fields_type='in',
      $offset=null,
      $maxrows=null
      )
  {
    extract($this->setting);

    if(empty($node) || !is_array($node) || !isset($node[$pre],$node[$succ]))
    {
      return false;
    }

    //if(intval($node[$succ]) - intval($node[$pre]) === 1)
    //{
    //  return null;
    //}
    
    $fields = (empty($output_fields))
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    $limit = (is_numeric($offset) && is_numeric($maxrows)) ? " LIMIT $offset,$maxrows" : null;

    $node_pre = intval($node[$pre]);
    $node_succ = intval($node[$succ]);

    $sql = "
      SELECT $fields
      FROM $table
      WHERE $pre BETWEEN $node_pre AND $node_succ 
      ORDER BY $pre ASC 
      $limit ;
    ";

    $subNodes = $this->dbh->query($sql);
    return $subNodes;
  }


  /**
   * @brief 获得节点路径
   */
  public function getNodePath($node, $output_fields='', $output_fields_type='in'){

    if(empty($node)){return false;}

    extract($this->setting);

    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    $sql = "
      SELECT $fields 
      FROM $table 
      WHERE $pre < {$node[$pre]} AND $succ > {$node[$succ]} 
    ORDER BY $pre ASC ;
    ";

    $nodes = $this->dbh->query($sql);
    return $nodes;    
  }

  /**
   * @brief 获得父节点
   */
  public function getParentNode($input_parentKey, $output_fields='',$output_fields_type='in')
  {

    if(empty($input_parentKey)){ return false;}

    extract($this->setting);

    $input_parentKey = SqlHelper::validateValue(
        (is_array($input_parentKey))
        ? $input_parentKey[$parentKey]
        : $input_parentKey
        );

    $fields = ($output_fields === '')
      ? '*'
      : $this->fields_format($output_fields, $output_fields_type);

    $sql = "
      SELECT $fields
      FROM $table
      WHERE $key=$input_parentKey
      LIMIT 1;
    ";

    $parentNode = $this->dbh->query($sql);

    return $parentNode;
  }

  /**
   * @brief 获得子节点个数
   */
  public function getChildCount($node)
  {
    extract($this->setting);

    if( empty($node) || !is_numeric($node[$succ]) || !is_numeric($node[$pre]) ){
      return false;
    }

    return ( $node[$succ] - $node[$pre] - 1 )/2;
  }

  /**
   * @brief 显示树
   */
  public function displayTree($start_node,$display_setting=null)
  {
    if(empty($start_node)){return false;}

    $nodes = $this->getSubNodes($start_node);   

    if(empty($nodes)){ return false;}

    extract($this->setting);


    $_display_setting = array(
        'type'  =>  'html',
        'mark'  =>  '--',

        'tree_tag' => 'ul',

        'tree_mark_type' => '',

        'tree_mark' => 'tree',

        //'item_template' =>  '<li><a href="#">%s</a></li>',
        'item_template' =>  '<li>%s</li>',

        'item_assign' => array($title),
        );

    $display_setting = (is_array($display_setting))
      ? array_merge($_display_setting,$display_setting)
      : $_display_setting;

    switch($display_setting['type'])
    {
      case 'txt':

        $result = $stack = array();

        $mark = $display_setting['mark'];
        foreach($nodes as $row)
        {
          if( count($stack)>0 )
          { 
            while( $stack[count($stack)-1] < $row[$succ] )
            {
              array_pop($stack); 
            }
          }
          $result[] = str_repeat($mark, count($stack)) . $row[$title];
          $stack[]    =   $row[$succ];
        }
        break;


      case 'html':

        $result = '';
        $stack = array();

        $tree_tag = $display_setting['tree_tag'];

        $item_template = $display_setting['item_template'];

        $item_assign = $display_setting['item_assign'];

        if(count($nodes) === 1)
        {
          $result = "<$tree_tag>" 
            .vsprintf($item_template,$nodes[0][$title])
            . "</$tree_tag>";    
          return $result;
        }

        foreach($nodes as $row)
        {
          if( count($stack)>0 )
          {
            while( $stack[count($stack)-1] < $row[$succ] )
            {
              $result .='</'.$tree_tag.'>';
              array_pop($stack); 
            }
          }

          $assign = array();

          foreach($item_assign as $item)
          {
            $assign[] = $row[$item]; 
          }

          $result     .= "<$tree_tag>".vsprintf($item_template, $assign);

          $stack[]    =   $row[$succ];
        }
        break;     
    }
    return $result;
  }

  /** 
   * @brief 插入节点
   * 
   * @param $targetNode 目标节点
   *
   * @param $referNode 参考节点
   *
   * @param $position  插入方式,默认值为'below',有如下值:
   * - after 插入到参考节点之后
   * - before 插入到参考节点之前
   * - below 插入到查考节点下一层
   *
   * @return boolean 
   */
  public function insertNode($referNode, $targetNode, $position='below')
  {
    extract($this->setting);

    if( !is_array($referNode) || !is_array($targetNode)
        || !array_key_exists($succ, $referNode)
        || !array_key_exists($pre, $referNode)
        || !array_key_exists($parentKey, $referNode) 
        || !array_key_exists($title, $targetNode)
      )
    {return false;}


    if($position === 'after')
    {
      $rootNode = $this->getRootNode($key);
      if($referNode[$key] === $rootNode[$key])
      {
        return false;  
      }

      $flag_update  = intval($referNode[$succ]);
      $flag_insert  = intval($referNode[$succ]);
      $flag_pkey    = $referNode[$parentKey];
    }
    elseif($position === 'before')
    {
      $rootNode = $this->getRootNode($key);
      if($referNode[$key] === $rootNode[$key])
      {
        return false;  
      }

      $flag_update  = intval($referNode[$pre])-1;
      $flag_insert  = $flag_update;
      $flag_pkey    = $referNode[$parentKey];
    }
    elseif($position === 'below')
    {
      $flag_update  = intval($referNode[$succ])-1;
      $flag_insert  = $flag_update;
      $flag_pkey    = $referNode[$key]; 
    }
    else
    {
      return false;  
    }


    $sqlUpdatePre = "
      UPDATE $table
      SET $pre = $pre+2 
      WHERE $pre > $flag_update ;
    ";

    $sqlUpdateSucc ="
      UPDATE $table 
      SET $succ = $succ + 2 
      WHERE $succ > $flag_update ;
    ";

    $targetNode[$pre]       = $flag_insert + 1;
    $targetNode[$succ]      = $flag_insert + 2;
    $targetNode[$parentKey] = $flag_pkey ;

    $sqlInsert = "INSERT INTO $table" . SqlHelper::buildArray($targetNode,'insert');

    if(method_exists($this->dbh, 'startTrans') 
        && method_exists($this->dbh, 'commit')
        && method_exists($this->dbh, 'rollback')
      )
    {
      try
      {
        $this->dbh->startTrans();
        $this->dbh->execute( $sqlUpdatePre);
        $this->dbh->execute( $sqlUpdateSucc);
        $this->dbh->execute($sqlInsert);
        $this->dbh->commit();

        return true;
      }
      catch(Exception $e)
      {   
        $this->dbh->rollback();
        return flash;
      }   
    }
    else
    {
      try
      {
        $this->dbh->execute( $sqlUpdatePre);
        $this->dbh->execute( $sqlUpdateSucc);
        $this->dbh->execute($sqlInsert);
      }
      catch(Exception $e) 
      {
        return false; 
      }
    }
  }

  /**
   * @brief 删除节点
   */
  public function deleteNode($node)
  {
    extract($this->setting);

    if( !is_array($node) || !array_key_exists($pre, $node) || !array_key_exists($succ, $node))
    {return false;}

    $flag_pre     =   intval($node[$pre]);
    $flag_succ    =   intval($node[$succ]);
    $flag_width   =   $flag_succ - $flag_pre + 1 ;

    $sqlDelete = "
      DELETE FROM $table 
      WHERE $pre BETWEEN $flag_pre AND $flag_succ ;
    ";

    $sqlUpdatePre = "
      UPDATE $table 
      SET $pre = $pre-$flag_width 
      WHERE $pre > $flag_pre ; 
    ";

    $sqlUpdateSucc = "
      UPDATE $table
      SET $succ = $succ - $flag_width 
      WHERE $succ > $flag_succ ;
    ";

    if(method_exists($this->dbh, 'startTrans') 
        && method_exists($this->dbh, 'commit')
        && method_exists($this->dbh, 'rollback')
      )
    {
      try
      {
        $this->dbh->startTrans();
        $this->dbh->execute( $sqlDelete);
        $this->dbh->execute( $sqlUpdatePre);
        $this->dbh->execute( $sqlUpdateSucc);
        $this->dbh->commit();

        return true;
      }
      catch(Exception $e)
      {   
        $this->dbh->rollback();
        return flash;
      }   
    }
    else
    {
      try
      {
        $this->dbh->execute( $sqlUpdatePre);
        $this->dbh->execute( $sqlUpdateSucc);
        $this->dbh->execute($sqlInsert);
      }
      catch(Exception $e) 
      {
        return false; 
      }
    }
  }

  /**
   * @brief 删除全部节点
   */
  public function deleteAllNodes()
  {

    $sql = "DELETE FROM {$this->setting['table']} ;";
    if($this->dbh->execute($sql)){

      return true; 
    }
    else{
      return false;
    }
  }


  /**
   * @brief 更新节点
   */
  public function updateNodes($condition,array $data){

    extract($this->setting);

    unset($data[$pre], $data[$succ], $data[$parentKey]); 

    $update = SqlHelper::buildArray($data, 'update');

    $sql = "
      UPDATE {$this->setting['table']}
    SET $update
      WHERE $condition
      ";

    if($this->dbh->execute($sql))
    {
      return true;  
    }
    else
    {
      return false; 
    }
  }

  /**
   * @brief 初始化,插入根节点
   */
  public function init($root)
  {
    if(!is_array($root) || empty($root))
    {
      return false;  
    }

    extract($this->setting);

    $root['pre'] = 1;
    $root['succ'] =2;

    $sql = 'INSERT INTO '.$table . SqlHelper::buildArray($root, 'insert');

    if($this->dbh->execute($sql)){
      return true;
    }
    else{
      return false;
    }
  }

}
